﻿namespace Sai.Core.Reporting
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using Sai.Core.Sandcastle;
    using Sai.Core.Introspection;
	
	/// <summary>
	/// Builder used to create reports on API differences.
	/// </summary>
	public class ReportBuilder
	{
		public ReportBuilder(IProductReleaseDifference releaseDifference, IEnumerable<IAssemblyDifference> assemblyDifferences, ProductReleaseProject project, IReportFormatterFactory factory)
		{
			this.ReleaseDifference = releaseDifference;
			this.AssemblyDifferences = new List<IAssemblyDifference>(assemblyDifferences);
			this.ReportFormatterFactory = factory;
			this.Project = project;
		}

		public event EventHandler<CancelBuildEventArgs> BuildStarting;

        public event EventHandler<CancelBuildEventArgs> PreBuildStarting;

        public event EventHandler<CancelBuildEventArgs> PreBuildComplete;

		public event EventHandler<FileProgessEventArgs> CreatingReport;

        public event EventHandler<CancelBuildEventArgs> ReportComplete;

        public event EventHandler<CancelBuildEventArgs> PostBuildStarting;

        public event EventHandler<CancelBuildEventArgs> PostBuildComplete;

		public event EventHandler<BuildErrorEventArgs> BuildError;

		public event EventHandler BuildComplete;

		public event EventHandler<BuildEventArgs> BuildEvent;

		private ProductReleaseProject Project { get; set; }

		private IProductReleaseDifference ReleaseDifference { get; set; }

		private List<IAssemblyDifference> AssemblyDifferences { get; set; }

		private IReportFormatterFactory ReportFormatterFactory { get; set; }

		private string SummaryName { get; set; }

		private List<ReportIndex> generatedReportList = new List<ReportIndex>();

        public void Build(IReportProgress progress)
		{
            try
			{
                this.OnBuildStarting();

				this.ExecutePreBuildSteps();

				this.BuildInternal(progress);

				this.ExecutePostBuildSteps();
            
                this.OnBuildComplete();
            }
            catch (BuildCancelledException)
            {
                throw;
            }
            catch (Exception ex)
			{
				this.OnError(ex);
			}
        }

		private void ExecutePreBuildSteps()
		{
			this.OnPreBuildStarting();
			this.CheckPreconditions();

			this.SummaryName = this.ReleaseDifference.Name;

			if (!Directory.Exists(this.Project.OutputFolder))
			{
				Directory.CreateDirectory(this.Project.OutputFolder);
			}

			if (this.Project.CleanOutput)
			{
				var storeFolder = new DirectoryInfo(this.Project.OutputFolder);

				foreach (FileInfo existingOutputFile in storeFolder.GetFiles(FileConstants.OutputFilter))
				{
					File.SetAttributes(existingOutputFile.FullName, FileAttributes.Normal);
					File.Delete(existingOutputFile.FullName);
				}
			}

			// fix up ids so file references will match
			if (!string.IsNullOrEmpty(this.Project.TopicId))
			{
				this.ReleaseDifference.Identifier = this.Project.TopicId;
			}

			string summaryID = this.ReleaseDifference.Identifier;

			foreach (var assembly in this.AssemblyDifferences)
			{
				ProductAssembly pa = this.ReleaseDifference.FindAssembly(assembly.Name);

				if (pa != null)
				{
					assembly.SummaryIdentifier = summaryID;
					assembly.SummaryName = this.SummaryName;
					assembly.Identifier = pa.Identifier;
					assembly.Name = pa.Name;
				}
			}

			this.OnPreBuildComplete();
		}

		private void CheckPreconditions()
		{
			/* nothing */
		}

        private void BuildInternal(IReportProgress progress)
		{
            //System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();

            //stopwatch.Start();

			// assemblies + summary
			int totalItems = this.AssemblyDifferences.Count + 1;
			int reportCount = 0;

            // write style pages if required
            // write js pages if required

            IReportFormatter reportFormatter = this.ReportFormatterFactory.Create();

			IReportOutputFactory outputFactory = new ReportOutputFactory(this.Project.OutputFolder, reportFormatter.Extension);
			
            this.OnCreatingReport(this.SummaryName, ++reportCount, totalItems);

            IReportOutput summaryReportOutput = outputFactory.Create(this.ReleaseDifference);

			ReportIndex summaryIndex = new ReportIndex(this.ReleaseDifference, summaryReportOutput);

			this.generatedReportList.Add(summaryIndex);

            reportFormatter.Execute(this.ReleaseDifference, this.Project.SummaryTransformer, summaryReportOutput); 

			this.OnReportComplete();
            
            // now output individual assembly reports...
            foreach (var assemblyDifference in this.AssemblyDifferences)
			{
                IReportOutput assemblyReportOutput = outputFactory.Create(assemblyDifference);

				ReportIndex assemblyIndex = new ReportIndex(assemblyDifference, assemblyReportOutput);

				this.generatedReportList.Add(assemblyIndex);

				this.OnCreatingReport(assemblyDifference.Name, ++reportCount, totalItems);

				reportFormatter.Execute(assemblyDifference, this.Project.AssemblyTransformer, assemblyReportOutput); 

				this.OnReportComplete();

				foreach (var typeDifference in assemblyDifference.ChangeDetails)
				{
                    IReportOutput typeReportOutput = outputFactory.Create(typeDifference);

					ReportIndex typeIndex = new ReportIndex(typeDifference, typeReportOutput); 

					this.generatedReportList.Add(typeIndex);

					this.OnCreatingReport(typeDifference.Name, ++reportCount, totalItems);

                    reportFormatter.Execute(typeDifference, this.Project.TypeTransformer, typeReportOutput); 

					this.OnReportComplete();
				}
			}

            // now add global added, removed, obsolete reports

            //if (this.Project.ReportOnAdditions)
            {
                var addedTypesFactory = new AddedTypesListingFactory();
                var addedTypes = addedTypesFactory.Create(this.AssemblyDifferences);
                addedTypes.SummaryIdentifier = this.ReleaseDifference.Identifier;
                addedTypes.SummaryName = this.ReleaseDifference.Name;

                // report on both
                IReportOutput addedTypesOverviewOutput = outputFactory.Create(addedTypes);

                ReportIndex addedTypeIndex = new ReportIndex(addedTypes, addedTypesOverviewOutput);
                this.generatedReportList.Add(addedTypeIndex);

                reportFormatter.Execute(addedTypes, this.Project.ListingTransformer, addedTypesOverviewOutput);
            }

            //if (this.Project.ReportOnRemovals)
            {
                var removedTypesFactory = new RemovedTypesListingFactory();
                var removedTypes = removedTypesFactory.Create(this.AssemblyDifferences);
                removedTypes.SummaryIdentifier = this.ReleaseDifference.Identifier;
                removedTypes.SummaryName = this.ReleaseDifference.Name;

                IReportOutput removedTypesOverviewOutput = outputFactory.Create(removedTypes);

                ReportIndex removedTypeIndex = new ReportIndex(removedTypes, removedTypesOverviewOutput);
                this.generatedReportList.Add(removedTypeIndex);

                reportFormatter.Execute(removedTypes, this.Project.ListingTransformer, removedTypesOverviewOutput);
            }

            //stopwatch.Stop();
            //long elapsed = stopwatch.ElapsedMilliseconds;

            //// 89 - 90,000 ms down to 5,500 ish.
            //if (elapsed > 0)
            //{
                
            //}
		}
			
		private void ExecutePostBuildSteps()
		{
			this.OnPostBuildStarting();

			// do sandcastle work here...
			// needs refactoring once working...
			if (!string.IsNullOrEmpty(this.Project.SandcastleProject) 
				&& !string.IsNullOrEmpty(this.Project.TopicId)
				&& this.Project.ReportFormat == ReportFormat.Maml)
			{
				bool discoveredProject = File.Exists(this.Project.SandcastleProject);

				if (!discoveredProject && !Path.IsPathRooted(this.Project.SandcastleProject))
				{
					this.Project.SandcastleProject = this.Project.SandcastleProject.AbsoluteFromFolder(Environment.CurrentDirectory);

					discoveredProject = File.Exists(this.Project.SandcastleProject);
				}

				if (discoveredProject)
				{
					// create a sandcastle project builder and sort it ...
					ProjectBuilder builder = new ProjectBuilder(this.generatedReportList)
					{
						SandcastleProject = this.Project.SandcastleProject,
						TopicId = this.Project.TopicId
					};

					builder.BuildError += (s, e) =>
					{
						this.OnBuildEvent("ProjectBuilder error : " + e.Message);
					};

					builder.Build();
				}

				this.OnPostBuildComplete();
			}
		}

		private void OnBuildStarting()
		{
            CancelBuildEventArgs args = new CancelBuildEventArgs();

			this.BuildStarting.Fire(this, args);

            if (args.CancelAction)
                throw new BuildCancelledException();
		}

		private void OnPreBuildStarting()
		{
            CancelBuildEventArgs args = new CancelBuildEventArgs();
            
            this.PreBuildStarting.Fire(this, args);
        
            if (args.CancelAction)
                throw new BuildCancelledException();
        }

		private void OnPreBuildComplete()
		{
            CancelBuildEventArgs args = new CancelBuildEventArgs();
            
            this.PreBuildComplete.Fire(this, args);

            if (args.CancelAction)
                throw new BuildCancelledException();
		}

		private void OnCreatingReport(string file, int item, int itemTotal)
		{
            FileProgessEventArgs args = new FileProgessEventArgs(file, item, itemTotal);

            this.CreatingReport.Fire(this, args);

            if (args.CancelAction)
                throw new BuildCancelledException();
		}

		private void OnReportComplete()
		{
            CancelBuildEventArgs args = new CancelBuildEventArgs();

            this.ReportComplete.Fire(this, args);
        
            if (args.CancelAction)
                throw new BuildCancelledException();
        }

		private void OnPostBuildStarting()
		{
            CancelBuildEventArgs args = new CancelBuildEventArgs();
            
            this.PostBuildStarting.Fire(this, args);

            if (args.CancelAction)
                throw new BuildCancelledException();
		}

		private void OnBuildEvent(string message)
		{
            CancelBuildEventArgs args = new CancelBuildEventArgs();

            this.BuildEvent.Fire(this, new BuildEventArgs(message));
        
            if (args.CancelAction)
                throw new BuildCancelledException();
        }

		private void OnPostBuildComplete()
		{
            CancelBuildEventArgs args = new CancelBuildEventArgs();

            this.PostBuildComplete.Fire(this, args);

            if (args.CancelAction)
                throw new BuildCancelledException();
		}

		private void OnError(Exception ex)
		{
			this.BuildError.Fire(this, new BuildErrorEventArgs(ex));
		}

		private void OnBuildComplete()
		{
			this.BuildComplete.Fire(this);
		}
	}
}
